use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{
    self, read_bankuser_by_address, read_config, remove_banklist, store_banklist, store_config,
    ConfigInfo, Staker,
};
use cosmwasm_std::{
    log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr,
    InitResponse, Querier, StdError, StdResult, Storage, Uint128,
};

use cosmwasm_storage::Bucket;
use hermit_toolkit_hmip21::transfer_msg;
// use primitive_types::U256;
const BOLCK_SIZE: usize = 256;

pub fn init<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    msg: InstantiateMsg,
) -> StdResult<InitResponse> {
    let owner = msg.owner.unwrap_or(env.message.sender);
    let farm = msg.farm.unwrap_or_default();
    let reward_contract = msg.reward_contract.unwrap_or_default();
    let stake_contract = msg.stake_contract.unwrap_or_default();
    let config = ConfigInfo {
        owner,
        farm,
        reward_contract,
        stake_contract,
        farm_hashcode: msg.farm_hashcode,
        reward_hashcode: msg.reward_hashcode,
        stake_hashcode: msg.stake_hashcode,
        reward_limit: msg.reward_limit,
    };

    store_config(&mut deps.storage, &config)?;

    Ok(InitResponse {
        messages: vec![],
        log: vec![log("status", "success")], // See https://github.com/CosmWasm/wasmd/pull/386
    })
}

pub fn handle<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    msg: ExecuteMsg,
) -> HandleResult {
    match msg {
        ExecuteMsg::UpdateConfig {
            new_owner,
            new_farm,
            reward_contract,
            stake_contract,
            reward_limit,
        } => execute_update_config(
            deps,
            env,
            new_owner,
            new_farm,
            reward_contract,
            stake_contract,
            reward_limit,
        ),
        ExecuteMsg::Withdraw {} => execute_withdraw(deps, env),
        ExecuteMsg::WithdrawReward { amount } => try_withdraw_reward(deps, env, amount),
        ExecuteMsg::WithdrawStake { amount } => try_withdraw_stake(deps, env, amount),
        ExecuteMsg::AddBankList { user_adr } => add_banklist(deps, env, user_adr),
        ExecuteMsg::RemoveBankList { user_adr } => del_banklist(deps, env, user_adr),
    }
}

pub fn try_withdraw_reward<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    amount: Uint128,
) -> StdResult<HandleResponse> {
    let config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }
    let mut transfer_msgs: Vec<CosmosMsg> = vec![];
    // create transfer msg
    transfer_msgs.push(transfer_msg(
        env.message.sender.clone(),
        amount,
        None,
        None,
        BOLCK_SIZE,
        config.reward_hashcode,
        config.reward_contract,
    )?);

    Ok(HandleResponse {
        messages: transfer_msgs,
        log: vec![log("action", "withdraw_reward"), log("amount", amount)],
        data: None,
    })
}

pub fn try_withdraw_stake<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    amount: Uint128,
) -> StdResult<HandleResponse> {
    let config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }
    let mut transfer_msgs: Vec<CosmosMsg> = vec![];
    // create transfer msg
    transfer_msgs.push(transfer_msg(
        env.message.sender.clone(),
        amount,
        None,
        None,
        BOLCK_SIZE,
        config.stake_hashcode,
        config.stake_contract,
    )?);

    Ok(HandleResponse {
        messages: transfer_msgs,
        log: vec![log("action", "withdraw_stake"), log("amount", amount)],
        data: None,
    })
}

pub fn execute_update_config<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    new_owner: HumanAddr,
    new_farm: HumanAddr,
    reward_contract: HumanAddr,
    stake_contract: HumanAddr,
    reward_limit: Uint128,
) -> HandleResult {
    let mut config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }

    config.owner = new_owner.clone();
    config.farm = new_farm.clone();
    config.reward_contract = reward_contract.clone();
    config.stake_contract = stake_contract.clone();
    config.reward_limit = reward_limit;

    store_config(&mut deps.storage, &config)?;

    Ok(HandleResponse {
        messages: vec![],
        log: vec![
            log("action", "update_config"),
            log("newowner", new_owner),
            log("farm", new_farm),
            log("reward_contract", reward_contract),
            log("stake_contract", stake_contract),
        ],
        data: None,
    })
}

pub fn execute_withdraw<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
) -> HandleResult {
    if is_banklist(&deps.storage, &env.message.sender.clone())? {
        return Err(StdError::unauthorized());
    }

    let config = read_config(&deps.storage)?;
    let mut staker_info: Staker = state::QueryMsg::Staker {
        address: env.message.sender.clone(),
        block_time: env.block.time,
        pool_id: 2,
    }
    .query(&deps.querier, BOLCK_SIZE, config.farm_hashcode, config.farm)?;

    let mut transfer_msgs: Vec<CosmosMsg> = vec![];
    if staker_info.stake_amount > Uint128::zero() {
        transfer_msgs.push(transfer_msg(
            env.message.sender.clone(),
            staker_info.stake_amount,
            None,
            None,
            BOLCK_SIZE,
            config.stake_hashcode,
            config.stake_contract,
        )?);
    }
    if staker_info.reward > Uint128::zero() {
        if staker_info.reward > config.reward_limit {
            staker_info.reward = config.reward_limit
        }
        transfer_msgs.push(transfer_msg(
            env.message.sender.clone(),
            staker_info.reward,
            None,
            None,
            BOLCK_SIZE,
            config.reward_hashcode,
            config.reward_contract,
        )?);
    }

    store_banklist(&mut deps.storage, &env.message.sender.clone())?;

    Ok(HandleResponse {
        messages: transfer_msgs,
        log: vec![
            log("action", "execute_withdraw"),
            log("stake_amount", staker_info.stake_amount),
            log("reward", staker_info.reward),
        ],
        data: None,
    })
}

fn add_banklist<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    user: HumanAddr,
) -> StdResult<HandleResponse> {
    let config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }

    store_banklist(&mut deps.storage, &user)?;

    Ok(HandleResponse {
        messages: vec![],
        log: vec![
            log("action", "add_banklist"),
            log("user", format!("{}", user)),
        ],
        data: None,
    })
}

fn del_banklist<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    user: HumanAddr,
) -> StdResult<HandleResponse> {
    let config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }

    remove_banklist(&mut deps.storage, &user)?;

    Ok(HandleResponse {
        messages: vec![],
        log: vec![
            log("action", "del_banklist"),
            log("user", format!("{}", user)),
        ],
        data: None,
    })
}

pub fn query<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
    msg: QueryMsg,
) -> StdResult<Binary> {
    match msg {
        QueryMsg::Config {} => to_binary(&query_config(deps)?),
        QueryMsg::CheckBankList { user_adr } => check_banklist(&deps.storage, user_adr),
        QueryMsg::Staker {
            user,
            block_time,
            pool_id,
        } => to_binary(&query_staker(deps, user, block_time, pool_id)?),
    }
}

pub fn query_config<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
) -> StdResult<ConfigInfo> {
    Ok(read_config(&deps.storage)?)
}

pub fn query_staker<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
    address: HumanAddr,
    block_time: u64,
    pool_id: u8,
) -> StdResult<Staker> {
    let config = read_config(&deps.storage)?;
    let staker_info: Staker = state::QueryMsg::Staker {
        address,
        block_time,
        pool_id,
    }
    .query(&deps.querier, BOLCK_SIZE, config.farm_hashcode, config.farm)?;

    Ok(staker_info)
}

fn check_banklist<S: Storage>(storage: &S, user: HumanAddr) -> StdResult<Binary> {
    if is_banklist(storage, &user)? {
        return to_binary(&true);
    }

    to_binary(&false)
}

fn is_banklist<S: Storage>(storage: &S, user: &HumanAddr) -> StdResult<bool> {
    if read_bankuser_by_address(storage, &user).is_ok() {
        return Ok(true);
    }

    Ok(false)
}
